<?php


namespace EchoPHP\Cache;

use Exception;
use EchoPHP\Filesystem\Filesystem;

class FileStore implements CacheContract {

    /**
     * 文件操作类
     * @var Filesystem Filesystem
     */
    protected $filesystem;
    /**
     * 缓存文件存储路径
     * @var
     */
    protected $directory;

    public function __construct(Filesystem $filesystem, $directory) {
        $this->filesystem = $filesystem;
        $this->directory = $directory;
    }

    /**
     * @inheritDoc
     */
    public function has($key) {
        return !is_null($this->get($key));
    }

    /**
     * @inheritDoc
     */
    public function pull($key, $default = null) {
        $content = $this->getPayload($key);
        if (!isset($content['data']) || is_null($content['data'])) {
            return $default;
        }
        $this->delete($key);
        return $content['data'];
    }

    /**
     * @inheritDoc
     */
    public function get($key, $default = null) {
        $content = $this->getPayload($key);
        if (!isset($content['data']) || is_null($content['data'])) {
            return $default;
        }
        return $content['data'];
    }

    /**
     * @inheritDoc
     */
    public function set($key, $value, $seconds) {
        $path = $this->path($key);
        $this->ensureDirectoryExists(dirname($path));

        $result = $this->filesystem->put(
            $path, $this->expirationToTimestamp($seconds) . serialize($value), true
        );
        return $result !== false && $result > 0;
    }

    /**
     * @inheritDoc
     */
    public function add($key, $value, $seconds) {
        if ($this->has($key)) {
            return false;
        }
        return $this->set($key, $value, $seconds);
    }

    /**
     * @inheritDoc
     */
    public function increment($key, $step = 1) {
        $content = $this->getPayload($key);
        $data = $content['data'] ?? 0;
        $data = intval($data) + $step;
        $seconds = $content['time'] ?? 0;
        if (!$this->set($key, $data, $seconds)) {
            return false;
        }
        return $data;
    }

    /**
     * @inheritDoc
     */
    public function decrement($key, $step = 1) {
        $content = $this->getPayload($key);
        $data = $content['data'] ?? 0;
        $data = intval($data) - $step;
        $seconds = $content['time'] ?? 0;
        if (!$this->set($key, $data, $seconds)) {
            return false;
        }
        return $data;
    }

    /**
     * @inheritDoc
     */
    public function delete($key) {
        $path = $this->path($key);
        return $this->filesystem->delete($path);
    }

    /**
     * @inheritDoc
     */
    public function flush() {
        if (!$this->filesystem->isDirectory($this->directory)) {
            return false;
        }
        foreach ($this->filesystem->directories($this->directory) as $directory) {
            if (!$this->filesystem->deleteDirectory($directory)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取key值对应的完整的存储路径
     * @param $key
     * @return string
     */
    protected function path($key) {
        $parts = array_slice(str_split($hash = sha1($key), 2), 0, 2);

        return $this->directory . '/' . implode('/', $parts) . '/' . $hash;
    }

    /**
     * 如果目录不存在则创建目录，确保缓存目录有效
     * @param $directory
     */
    protected function ensureDirectoryExists($directory) {
        if (!$this->filesystem->exists($directory)) {
            $this->filesystem->makeDirectory($directory, 0777, true, true);
        }
    }

    /**
     * 获取有效期截止时间戳
     * @param int $seconds
     * @return int
     */
    protected function expirationToTimestamp($seconds) {
        $seconds = intval($seconds);
        $currentTimeStamp = time();
        if ($seconds > $currentTimeStamp) {
            //设置的是过期时间戳
            $time = $seconds;
        } else {
            //设置的是过期秒
            $time = $currentTimeStamp + $seconds;
        }

        return $seconds === 0 || $time > 9999999999 || $time < 1000000000 ? 9999999999 : $time;
    }

    /**
     * 载入缓存文件内容及剩余有效时长
     * @param $key      缓存键名
     * @return array
     */
    protected function getPayload($key) {
        $path = $this->path($key);

        try {
            $expire = substr(
                $contents = $this->filesystem->get($path, true), 0, 10
            );
        } catch (Exception $e) {
            //缓存文件不存在，直接返回空内容
            return $this->emptyPayload();
        }

        if (time() >= $expire) {
            //缓存已过期，删除旧的缓存文件
            $this->delete($key);
            return $this->emptyPayload();
        }

        try {
            $data = unserialize(substr($contents, 10));
        } catch (Exception $e) {
            //文件内容反序列化失败，内容无法获取
            $this->delete($key);
            return $this->emptyPayload();
        }
        //获取缓存内容剩余有效时长
        $time = $expire - time();
        return compact('data', 'time');
    }

    /**
     * 载入空内容
     * @return array
     */
    protected function emptyPayload() {
        return ['data' => null, 'time' => null];
    }
}